/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package HTTPDownloader;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

/**
 *
 * @author patricksamson236
 */
public class DownloadWorker extends Thread {

    // Worker info
    private int WorkerID;
    private Status status;
    // HDD IO
    private RandomAccessFile file;
    private File destination;
    // connections
    private InputStream stream;
    private HttpURLConnection connection;
    private URL url;
    // sizes
    private long connSize;
    private long downSession;
    private long downLastInst;
    // positions
    private Range range;
    private int MAX_BUFFER_SIZE;
    //private long connStartPos;
    //private long connEndPos;
    // time
    private double timeSessionStart;
    private double timeLastInstant;
    // speeds
    private double speedLastSessionAvg;
    private double speedLastInst;

    public DownloadWorker(URL url, int WorkerID,
            Range range, int maxBufferSize, File destination) {
        //reset vars
        newRangeReset();

        /*
         * if (range.getStart() != 0) { range.setStart(range.getStart() - 1); }
         *
         */

        this.MAX_BUFFER_SIZE = maxBufferSize;
        this.url = url;
        this.WorkerID = WorkerID;
        this.range = range;
        //this.connStartPos = startPos;
        //this.connEndPos = endPos;
        this.destination = destination;

        System.out.println("Worker " + this.WorkerID + " : " + this.range.getStart() + " - " + this.range.getEnd());
    }

    public void stopWorker() {
        status = status.STOPPED;
    }

    public void setRangeDone() {
        this.range = null;
        this.status = status.AVAILABLE;
        newRangeReset();
    }

    private void newRangeReset() {
        connSize = -1;
        downSession = 0;
        downLastInst = 0;
    }

    public Object[] saveHttpWorker() {
        Object[] data = new Object[2];
        ArrayList<String> names = new ArrayList<String>();
        ArrayList<String> values = new ArrayList<String>();

        names.add("WorkerID");
        values.add(WorkerID + "");

        names.add("Status");
        values.add(status.toString());

        names.add("Size");
        values.add(connSize + "");

        names.add("StartPos");
        values.add(range.getStart() + "");

        names.add("End");
        values.add(range.getEnd() + "");

        data[0] = names;
        data[1] = values;
        return data;
    }

    @Override
    public void run() {
        timeSessionStart = System.nanoTime();
        timeLastInstant = timeSessionStart;
        file = null;
        stream = null;
        status = Status.CONNECTING;
        stateChanged();

        try {
            // Open connection to URL.
            connection = (HttpURLConnection) url.openConnection();

            // Specify what portion of file to download.
            connection.setRequestProperty("Range",
                    "bytes=" + range.getStart() + "-" + range.getEnd());

            // Connect to server.
            connection.connect();

            // Make sure response code is in the 200 range.
            if (connection.getResponseCode() / 100 != 2) {
                error();
                System.out.println("Server's response code produced an error! ( "
                        + connection.getResponseCode() + " )\n"
                        + connection.getResponseMessage());
            }

            // Check for valid content length.
            int contentLength = connection.getContentLength();
            if (contentLength < 1) {
                error();
                System.out.println("Content legnth cannot be negative!");
            }

            /*
             * Set the size for this download if it hasn't been already set.
             */
            if (connSize == -1) {
                connSize = contentLength;
            }

            // Open file and seek to the right position.
            file = new RandomAccessFile(destination, "rw");
            file.seek(range.getStart());

            stream = connection.getInputStream();
            status = Status.DOWNLOADING;
            while (status == Status.DOWNLOADING) {
                /*
                 * Size buffer according to how much of the file is left to
                 * download.
                 */
                byte buffer[];
                if (connSize - downSession > MAX_BUFFER_SIZE) {
                    buffer = new byte[MAX_BUFFER_SIZE];
                } else if (range.getLength() <= range.getDownloaded()) {
                    break; //exit the while loop
                } else if (connSize <= downSession) {
                    break; //exit the while loop
                } else {
                    buffer = new byte[Integer.parseInt((connSize - downSession) + "")];
                }

                // Read from server into buffer.
                int read = stream.read(buffer);
                if (read == -1) {
                    break;
                }

                // Write buffer to file.
                file.write(buffer, 0, read);

                // Everything's fine!
                downSession += read;
                range.addDownloaded(read);
            }

            /*
             * Change status to complete if this point was reached because
             * downloading has finished.
             */
            if (status == Status.DOWNLOADING) {
                status = Status.DONE;
                stateChanged();
            }
        } catch (Exception e) {
            error();
        } finally {
            // Close file.
            if (file != null) {
                try {
                    file.close();
                } catch (Exception e) {
                }
            }

            // Close connection to server.
            if (stream != null) {
                try {
                    stream.close();
                    connection.disconnect();
                } catch (Exception e) {
                }
            }
        }
    }

    private void error() {
        System.out.println("There Was An Error!");
        status = Status.ERROR;
        stateChanged();
    }

    private void stateChanged() {
        if (status == Status.DOWNLOADING) {
            //manager.addActiveWorker();
        } else {
            //manager.removeActiveWorker();
        }
    }

    public long getSessionDownloaded() {
        return downSession;
    }

    public long getStartPos() {
        return range.getStart();
    }

    public long getRemainingSize() {
        return range.getRemaining();
    }

    public long getCurrentPosition() {
        long l = 0;
        try {
            l = range.getStart() + range.getDownloaded();
        } catch (Exception e) {
        }
        return l;
    }

    public Range getRangeCompleted() {
        return new Range(range.getStart(), getCurrentPosition());
    }

    public Range getRangeRemaining() {
        return new Range(getCurrentPosition(), range.getEnd());
    }

    public Range getFullRange() {
        return this.range;
    }

    public void setNewRange(Range range) {
        newRangeReset();
        this.range = range;
        this.start();
    }

    public long getSize() {
        return connSize;
    }

    public long getEndPos() {
        return (range.getStart() + connSize);
    }

    public long splitRemaining() {
        this.connSize = connSize - (getRemainingSize() / 2);
        return connSize;
    }

    public double getSessionAvgSpeed() {
        speedLastSessionAvg = downSession / ((System.nanoTime() - timeSessionStart) / 1000000000);
        return speedLastSessionAvg;
    }

    public double getInstSpeed() {
        speedLastInst = (downSession - downLastInst) / ((System.nanoTime() - timeLastInstant) / 1000000000);
        timeLastInstant = System.nanoTime();
        downLastInst = downSession;
        return speedLastInst;
    }

    public Status getStatus() {
        return status;
    }
}
